﻿using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO;

namespace AGage.PolarExplorer
{
    public partial class PolarPlot : UserControl
    {
        // Origin of the plot:
        private float oX = 20, oY = 15;

        static int[] TWS_PLOT = { 3, 6, 9, 12, 15, 20, 25, 30 };
        static int[] TWS_PLOT_LOW = { 3, 6, 9, 12, 15, 20, 25 };

        PerformancePolar polar;
        Graphics g;
        Pen bgPen, bgPenB;
        Brush bgBrush;
        Font bgFont, vmcFont;
        int bs_step, upperBS;
        double bs_max, vmg_max_bs, vmg_max_twa;
        double outerRadius;
        double R, H;
        bool displayVMGLines = false, displayVMCLines;//, lowInputTWS = false;
        int[] TWS;
        int maxTWS, vmgArraySz;
        ToolTip info = new ToolTip();

        public PolarPlot()
        {
            InitializeComponent();

            // initalise the Pens and other graphics objects:
            bgBrush = Brushes.Black;
            bgPen = new Pen(Brushes.LightGray, 0.5F);
            bgPenB = new Pen(Brushes.Black, 1F);
            bgFont = new Font("Arial", 10);
            vmcFont = new Font("Arial", 8);
        }        

        public bool DisplayVMGLines
        {
            set 
            { 
                displayVMGLines = value; 
                this.Invalidate();  
            }
        }

        public bool DisplayVMCLines
        {
            set
            {
                displayVMCLines = value; 
                this.Invalidate();
            }
        }

        public PerformancePolar Polar
        {
            get { return polar; }
            set
            {
                if (value == null)
                    return;
                polar = value;

                int twsi=polar.dataProvider.WindStrengths.Length - 1;
                if (polar.dataProvider.WindStrengths[twsi] < 15)
                {
                    TWS = TWS_PLOT_LOW;
                    //lowInputTWS = true;
                    maxTWS = 25;
                    vmgArraySz = 26;
                }
                else
                {
                    TWS = TWS_PLOT;
                    //lowInputTWS = false;
                    maxTWS = 30;
                    vmgArraySz = 31;
                }

                // compute max vmg
                double _max_vmg = 0;
                vmg_max_bs = vmg_max_twa = bs_max = 0;
                for (int tws = 10; tws <= maxTWS; ++tws)
                    for (int twa = 20; twa < 90; ++twa)
                    {
                        double bs = polar[twa, tws];
                        double vmg = bs * Math.Cos(twa * Math.PI / 180);
                        if (vmg > _max_vmg)
                        {
                            _max_vmg = vmg;
                            vmg_max_bs = bs;
                            vmg_max_twa = twa;
                        }
                    }
                
                // now max BS:
                for (int tws = 10; tws <= maxTWS; ++tws)
                    for (int twa = 91; twa <= 180; ++twa)
                    {
                        double bs = polar[twa, tws];
                        if (bs > bs_max)
                            bs_max = bs;
                    }

                // Now compute out size and bs step values:
                upperBS = (int)Math.Ceiling(bs_max);
                // step size for BS 'axis' arcs:
                bs_step = Math.Max(1, upperBS / 7);
                // Make sure our upper BS limit is a multiple of bs_step:
                while (upperBS % bs_step != 0)
                    ++upperBS;

                setSize();
            }
        }

        private void setSize()
        {
            if (polar == null)
                return;

            if (this.Height < 1)    // minimised
            {
                H = R = .1;
                return;
            }
            double n = 1;
            do
            {
                R = this.Width * n;
                H = Math.Cos(vmg_max_twa * Math.PI / 180) * vmg_max_bs / bs_max * R + 6;
                outerRadius = R * upperBS / bs_max;
                n -= 0.01;
            }
            while (outerRadius > this.Width - (oX * 2) || outerRadius + H > this.Height - oY - 10);
            this.Invalidate();
            Console.WriteLine("{0},{1} R={2} H={3}", Width, Height, R, H);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);
            if (polar == null)
                return;

            g = e.Graphics;
            g.Clear(Color.White);

            // Now draw the actual arcs:
            double rr = 0;
            double xx = oX - 18;
            for (int bs = bs_step; bs <= upperBS; bs += bs_step)
            {
                rr = R * bs / bs_max;
                double startAngle = (rr > H) ? Math.Acos(H / rr) : 0;
                drawBGArc(startAngle, rr);
                drawBGText(bs.ToString(), xx, oY + H + rr - 7);
            }

            // Now draw radials:
            for (int twa = 20; twa < 180; twa += 10)
            {
                double twa_rad = twa * RAD_CONV;
                double _xx = rr * Math.Sin(twa_rad);
                double _yy = -rr * Math.Cos(twa_rad);
                if (_yy < -H)
                {
                    _xx = H * Math.Tan(twa_rad);
                    _yy = -H;
                    drawBGText(twa.ToString() + '°', oX + _xx - 8, oY + H + _yy - 15);
                }
                else
                {
                    drawBGText(twa.ToString() + '°', oX + _xx + 2, oY + H + _yy);
                }
                g.DrawLine(bgPen, oX, (float)(oY + H), (float)(oX + _xx), (float)(oY + H + _yy));
            }

            // Bounding lines:
            g.DrawLine(bgPenB, oX, oY, (float)(oX + Math.Sqrt(rr * rr - H * H)), oY);
            g.DrawLine(bgPenB, oX, oY, oX, (float)(oY + H + rr));
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            g = e.Graphics;

            if (polar == null)
            {
                drawBGText("No Polar data set!", Width / 2 - 20, Height / 2 - 10);
                return;
            }

            float xt = (float)(oX + outerRadius * .95);
			float yt = (float)(oY + H + outerRadius - 110);

            PointF[] pnts;
            Pen p;
            foreach (int _tws in TWS)
            {
                pnts = new PointF[181];
                for (int twa = 0; twa <= 180; ++twa)
                {
                    pnts[twa] = bsAndTwa2xy(polar[twa, _tws], twa);
                }
                p = new Pen(windColourKNT(_tws), 2.25F);
                g.DrawCurve(p, pnts, 1);
                
                g.DrawLine(p, xt, yt, xt+10, yt);
                drawBGText(_tws.ToString() + " kn", xt + 12, yt - 7);
                yt += 15;
            }                        

            p = new Pen(Brushes.DarkGray, 1.5F);
            if (displayVMGLines)
            {
                PointF[] uw = new PointF[vmgArraySz];
                PointF[] dw = new PointF[vmgArraySz];
                uw[0] = dw[0] = bsAndTwa2xy(0, 0);
                for (int _tws = 1; _tws <= maxTWS; ++_tws)
                {
                    VelocityMadeGood vmg = polar.MaxUpwindVMG(_tws);
                    uw[_tws] = bsAndTwa2xy(vmg.velocity.speed, vmg.twa);
                    vmg = polar.MaxDownwindVMG(_tws);
                    dw[_tws] = bsAndTwa2xy(vmg.velocity.speed, vmg.twa);
                }
                g.DrawCurve(new Pen(Brushes.DarkGray, 2F), uw, 1F);
                g.DrawString("U", vmcFont, Brushes.DarkGray, uw[vmgArraySz-1]);
                g.DrawCurve(new Pen(Brushes.DarkGray, 2F), dw, 1F);
                g.DrawString("D", vmcFont, Brushes.DarkGray, dw[vmgArraySz - 1]);
            }

            if (displayVMCLines)
            {                
                for (int cc = 15; cc < 180; cc += 15)
                {
                    PointF[] pnt = new PointF[vmgArraySz];
                    pnt[0] = bsAndTwa2xy(0, 0);
                    for (int _tws = 1; _tws <= maxTWS; ++_tws)
                    {
                        VelocityMadeGood vmc = polar.MaxVMC(_tws, 0, cc);
                        pnt[_tws] = bsAndTwa2xy(vmc.velocity.speed, vmc.twa);
                    }
                    g.DrawCurve(p, pnt, 1F);
                    g.DrawString(cc.ToString() + '°', vmcFont, Brushes.DarkGray, pnt[vmgArraySz - 1]);
                }
            }
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            setSize();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            VelocityMadeGood vmg = xy2BsAndTwa(e.X - oX, e.Y - oY - (float)H);
            info.Show(string.Format("SOG={0}kn TWA={1}° VMG={2}kn",
                dToZ(vmg.velocity.speed, "0.00"), dToZ(vmg.twa, "0.00"), dToZ(vmg.vmg, "0.00")),
                this, e.X, e.Y);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            info.Hide(this);
        }
        
        private void drawBG()
        {
            // Now draw the actual arcs:
            double rr = 0;
            double xx = oX - 16;
			for (int bs = bs_step; bs <= upperBS; bs += bs_step)
            {
				rr = R * bs / bs_max; 
				double startAngle = (rr > H) ? Math.Acos(H / rr) : 0;
				drawBGArc(startAngle, rr);
                drawBGText(bs.ToString(),  xx, oY + H + rr - 7);
			}

            // Now draw radials:
            for (int twa = 20; twa < 180; twa += 10)
            {
                double twa_rad = twa * RAD_CONV;
                double _xx =  rr * Math.Sin(twa_rad);
                double _yy = -rr * Math.Cos(twa_rad);
                if (_yy < -H)
                {
                    _xx = H * Math.Tan(twa_rad);
                    _yy = -H;
                    drawBGText(twa.ToString() + '°', oX + _xx - 8, oY + H + _yy - 15);
                }
                else
                {
                    drawBGText(twa.ToString() + '°', oX + _xx, oY + H + _yy);
                }
                g.DrawLine(bgPen, oX, (float)(oY + H), (float)(oX + _xx), (float)(oY + H + _yy));
            }

            // Bounding lines:
            g.DrawLine(bgPenB, oX, oY, (float)(oX + Math.Sqrt(rr*rr - H*H)), oY);
            g.DrawLine(bgPenB, oX, oY, oX, (float)(oY + H + rr));
        }

        private void drawBGText(string z, double x, double y)
        {
            g.DrawString(z, bgFont, bgBrush, (float)x, (float)y);
        }

        private void drawBGArc(double startAngle, double radius)
        {
            startAngle -= HALF_PI;
            double stopAngle = HALF_PI;
            int sweepDeg = (int)((stopAngle - startAngle) * DEG_CONV);
            double angleStep = (stopAngle - startAngle) / sweepDeg;
            double x = oX + Math.Cos(startAngle) * radius;
            double y = oY + H + Math.Sin(startAngle) * radius;
            PointF[] pnts = new PointF[sweepDeg+1];
            pnts[0] = new PointF((float)x, (float)y);

            for (int i = 1; i <= sweepDeg ; ++i)
            {
                double angle = startAngle + i * angleStep;
                x = oX + Math.Cos(angle) * radius;
                y = oY + H + Math.Sin(angle) * radius;
                pnts[i] = new PointF((float)x, (float)y);
            }

            g.DrawCurve(bgPen, pnts, 1F);
        }

        private PointF bsAndTwa2xy(double bs, double twa)
		{
            twa *= (Math.PI / 180);
			double _r = R * bs / bs_max; 
			float x = (float)(oX + _r * Math.Sin(twa));
            float y = (float)(oY + H - _r * Math.Cos(twa)); 
			if (y < oY)
            {
				x = (float)(oX + H * Math.Tan(twa));
				y = oY;
			}
            return new PointF(x, y);
		}

        private VelocityMadeGood xy2BsAndTwa(float x, float y) 
		{
			double bs = Math.Sqrt(x*x + y*y) / R * bs_max;
			double twa = Math.PI - Math.Atan2(x / R, y / R);
            return new VelocityMadeGood(new Velocity(bs, 0), bs * Math.Cos(twa), twa * 180 / Math.PI, 0);
		}

        public static Color windColourKNT(double tws)
        {			
			int nRed;
			int nGreen;
			int nBlue;
			double dtws;
			
			if (tws < 10)			   // 0-10 knots
			{
				nRed = 0;
				nBlue = 255;
				nGreen = (int)((tws / 12.5) * 255);		 // [0, 10] -> [0, .8]			
			}
			else if (tws < 20)	 	  // 10-20 knots
			{
				dtws = (tws - 10) / 12.5; 
				nGreen = 255;
				nRed = (int)(dtws * 255);               // [10, 20] -> [0, .8]
				nBlue = (int)((.8 - dtws) * 255);	     // [10, 20] -> [.8, 0]
			}
			else if (tws < 30)		 // 20-30 knots
			{
				dtws = (tws - 20) / 12.5 ;
				nRed = 255;
				nBlue = 0;
				nGreen = (int)((.8 - dtws) * 255);		// [20, 30] -> [.8, 0]
			}
			else								// [30, ...]
			{
				nRed = 255;
				nGreen = nBlue = 0;
			}
            return Color.FromArgb(200, nRed, nGreen, nBlue);
		}

        private static readonly double DEG_CONV = 180 / Math.PI;
        private static readonly double RAD_CONV = Math.PI / 180;
        private static readonly double HALF_PI = Math.PI / 2;


        private string dToZ(double val, string fmt) 
        {
            return val.ToString(fmt, System.Globalization.CultureInfo.InvariantCulture); 
        }

        private void PolarPlot_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                DoFileDrop(e);
            else if (e.Data.GetDataPresent(DataFormats.UnicodeText))
                DoTextDrop(e);
            else if (e.Data.GetDataPresent(DataFormats.CommaSeparatedValue))
                DoCSVDrop(e);
        }

        private void PolarPlot_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop) ||
                e.Data.GetDataPresent(DataFormats.CommaSeparatedValue) ||
                e.Data.GetDataPresent(DataFormats.UnicodeText))
                e.Effect = DragDropEffects.Copy;
            else
                e.Effect = DragDropEffects.None;
        }

        private void DoCSVDrop(DragEventArgs e)
        {
            MessageBox.Show("Have CSV Data Drop!");
            string data = e.Data.GetData(DataFormats.CommaSeparatedValue).ToString();
            File.WriteAllText(@"D:\tmp\csvout.txt", data);
        }

        private void DoTextDrop(DragEventArgs e)
        {
            string data = e.Data.GetData(DataFormats.UnicodeText).ToString();
            this.Parent.Text = data.Split('\n')[0].Trim();
            // use property get for Polar to also compute size info etc:
            if (data.Contains(",") || data.Contains(";"))
            {
                Polar = new PerformancePolar(
                    CSVPolarProvider.FromStringData(data));
                this.Parent.Text = "CSV Drop";
            }
            else
                Polar = new PerformancePolar(
                                new TextPolarProvider(data));
        }
        
        private void DoFileDrop(DragEventArgs e)
        {
            try
            {
                Array a = (Array)e.Data.GetData(DataFormats.FileDrop);
                if (a != null)
                {
                    string sFile = a.GetValue(0).ToString();
                    int slash = sFile.LastIndexOf('\\') + 1;
                    int dot = sFile.LastIndexOf('.');
                    this.Parent.Text = sFile.Substring(slash, dot - slash);
                    if (sFile.EndsWith(".csv"))
                        Polar = new PerformancePolar(new CSVPolarProvider(sFile));
                    else
                        Polar = new PerformancePolar(
                                        new TextPolarProvider(File.ReadAllText(sFile)));
                }
            }
            catch (Exception)
            { }
        }
    }
}
